package org.hsc.proxy;
/*
 * HttpProxyHandler.java
 *
 * Created on December 22, 2007
 *
 * Name:        Moty Michaely   |   Alon Halfon
 * ID:          043413087       |   300545969
 */

import java.net.*;
import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * An http proxy handler that handles http requests.
 * 
 * @author Moty Michaely
 */
public class HttpProxyClientHandler {   
	private static final Logger logger=LoggerFactory.getLogger(HttpProxyClientHandler.class);
    /**
     * Client Response callback. The callback handles asynchronous http responses.
     */
    private class ResponseCallback implements HttpResponseCallback {
        /**
         * Handles Response callback
         */
        public synchronized void run(HttpContext context) {
            HttpResponse response = context.getResponse();
            HttpRequest request = context.getRequest();
            HttpProxyServer proxyServer = context.getProxyServer();
            Cache cache = context.getProxyServer().getCache();
            
            if (cache.isActive()) { 
                //cache is active- check if we got //304 (Not Modified)
                if (response.getStatusCode() == 304) { //we have up-to-date version of the file
                	
                    CacheComponent cachedData = cache.getCachedComponent(request.getCompleteUrl());
                    if( cachedData != null ){
                        context.setCacheResult(Cache.CacheResult.Hit);
                        response=cachedData.response;//take the response from the cache
                    }else {
                        context.setCacheResult(Cache.CacheResult.Miss);
                        //update the cache with the new response
                        // the key is the url of the reuqest, the value is the response
                        try {
                            cache.addCacheComponent(request.getCompleteUrl(), response);
                        } catch (Exception ex) {
                            logger.error("Response Callback Exception: " + ex.getMessage(),ex);
                        }
                    }
                } else {
                    context.setCacheResult(Cache.CacheResult.Miss);
                    //update the cache with the new response
                    // the key is the url of the reuqest, the value is the response
                    try {
                        cache.addCacheComponent(request.getCompleteUrl(), response);
                    } catch (Exception ex) {
                    	logger.error("Response Callback Exception: " + ex.getMessage(),ex);
                    }
                }
            }
            // Check if we should stop processing requests through this connection
            stop = !response.getKeepAlive();
            // Forward the response to the client
            int contentLength = response.getContentLength();
            Cache.Filtered filtered = Cache.Filtered.No;
            
            try {
                if (response.getContentLength() > proxyServer.getMaxFileSize()) {
                    HttpResponse.writeForbidden(clientSocket.getOutputStream());
                    filtered = Cache.Filtered.Yes;
                    contentLength = 164;
                }else {
                    response.write(clientSocket.getOutputStream());
                }
            } catch (IOException ioe ) {
            	logger.error("Response Callback IO Exception: " + ioe.getMessage(),ioe);
            } finally {
                Date now = new Date();
                
                DateFormat formatter = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
                String nowString=String.format("%1$s", formatter.format(now));
                String stringLength=Integer.toString(contentLength);
                String stringTotalTime=Long.toString(now.getTime() - context.getTimestamp().getTime());


                logger.info(String.format("%1$s, %2$s, %3$s, %4$s, %5$s, %6$s, %7$s, %8$s, %9$s",nowString,"request",context.getClientConnectionType(),context.getServerConnectionType(),context.getCacheResult(),request.getCompleteUrl(),stringLength,filtered,stringTotalTime));
            }
        }
    }
    
    /*
     * Holds the proxy server this client is being handled by.
     */
    private HttpProxyServer httpProxyServer=null;   
    
    /*
     * Determines whether to stop exchanging requestes and responses.
     */
    private volatile boolean stop = false;
    
    /*
     * The underlying tcp server socket of the client.
     */
    private Socket clientSocket = null;
    
    /** 
     * Creates a new instance of TcpServer 
     * 
     * @param  clientSocket    The client socket used for getting http requests from.
     */
    public HttpProxyClientHandler(Socket clientSocket,HttpProxyServer proxyServer)
    {
        this.clientSocket = clientSocket;
        this.httpProxyServer=proxyServer;
    }
    
    /**
     * Stops processing http requests.
     */
    public void stop()
    {
        stop = true;
        // TODO Implement close
    }
    
    /*
     * Disconnects the handler from the client.
     */ 
    protected void disconnectClient() throws IOException
    {
        if( this.clientSocket != null )
            this.clientSocket.close();
    }
    
    public void processRequests () {
        HttpRequest request = null;
        InputStream in = null;
        HttpContext context = null;
        HttpProxyServer.ConnectionType clientConnectionType = HttpProxyServer.ConnectionType.New;
        try {
            in = clientSocket.getInputStream();
        }catch( IOException ioe ) {
        	logger.error("Error getting input stream: " + ioe.getMessage(),ioe);
        }
        
        while( !stop ){
            try {
                // Parse A request...
            	ResponseCallback callback = new ResponseCallback();
                request = new HttpRequest(in);
                context = new HttpContext(request, clientConnectionType, this.httpProxyServer);
                // Route the request
                HttpProxyRouter.getInstance().route(context, callback);
                // Next connection type will be existing one.
                clientConnectionType = HttpProxyServer.ConnectionType.Existing;
            } catch( SocketException se ) {
               logger.error("Process Requests Socket Exception: " + se.getMessage(),se);
                stop = true;
            } catch( Exception e ) {
            	logger.error("Process Requests Exception: " + e.getMessage(),e);
                if( this.clientSocket != null )
                    stop = stop || this.clientSocket.isClosed();
            }
        }
        try {
            // disconnect from client.
            this.disconnectClient();
        }
        catch( IOException ioe ) {
        	logger.error("Error disconnecting from client: " + ioe.getMessage(),ioe);
        }
    }
    
    /** 
     * Starts processing of HTTP requests.
     */
    public void pr()
    {
//                // Check if we should stop processing requests through this connection
//                stop = !request.getKeepAlive();
//                date=new Date();
//                startTime=date.getTime();
//                // Connect to the server. If the server is already connected to the specified url, nothing happens. 
//                this.connectServer(request.getHost(), request.getPort());
//                // if the cache is active.
//                if (this.httpProxyServer.getCache().isActive())
//                {
//                    String url=request.getUrl().toString();
//                   //add if-modified-since
//                    cachedData=this.httpProxyServer.getCache().getCachedComponent(request.getCompleteUrl());
//                    if (cachedData!=null)//link is in cache- need to add if-modified-since
//                    {
//                        request.setIfModifiedSince(cachedData.getLastModified());
//                    }
//                }
//                else
//                   cacheResult=Log.CacheResult.NA;
//                // Forward the request to the following server
//                request.write(serverSocket.getOutputStream());
//                // Get the response
//                response = new HttpResponse(serverSocket.getInputStream());
//                if (this.httpProxyServer.getCache().isActive()) //cache is active- check if we got
//                                                                //304 (Not Modified)
//                {
//                    if (response.getStatusCode()==304)//we have up-to-date version of the file
//                    { 
//                        cacheResult=Log.CacheResult.Hit;
//                        response=cachedData.response;//take the response from the cache
//                        // Check if we should stop processing requests through this connection
//                    }    
//                    else
//                    {
//                        cacheResult=Log.CacheResult.Miss;
//                        //update the cache with the new response
//                        // the key is the url of the reuqest, the value is the response
//                        this.httpProxyServer.getCache().addCacheComponent(request.getCompleteUrl(), response);
//                    }
//                }
//                // Check if we should stop processing requests through this connection
//                stop = !response.getKeepAlive();
//                // Forward the response to the following server
//                if (response.getContentLength()>this.httpProxyServer.getMaxFileSize())
//                {
//                    logLength=178;
//                    logUrl="403 Forbidden";
//                    HttpResponse.writeForbidden(clientSocket.getOutputStream());
//                    logFilter=Log.Filtered.No;
//                }
//                else
//                {
//                    logLength=response.getContentLength();
//                    logUrl=request.getCompleteUrl();
//                    
//                    response.write(clientSocket.getOutputStream());
//                }
//               
//            }
//            catch( EmptyHttpRequest ehr ) 
//            {
//                // Read the entire streams
//                System.err.println("Empty Request.");
//                // Stop handling requests
//                stop = true;
//            }
//            catch( SocketTimeoutException ste )
//            {
//                System.err.println("Socket Timeout: " + ste.getMessage());
//                // Stop handling requests
//                stop = true;
//            }
//            catch( SocketException se )
//            {
//                StackTraceElement[] st = se.getStackTrace();
//                Log.getInstance().log("=== ERRORS ===");
//                for( int i=0; i<st.length; ++i )
//                    Log.getInstance().log(st[i].toString());
//                Log.getInstance().log("============");
//                System.err.println("Socket Exception: " + se.getMessage());
//                stop = true;
//            }
//            catch( Exception e )
//            {
//                System.err.println("Process Requests Exception: " + e.getMessage());
//                if( this.serverSocket != null )
//                    stop = stop || this.serverSocket.isClosed();
//                if( this.clientSocket != null )
//                    stop = stop || this.clientSocket.isClosed();
//            }
//            finally
//            {
//                date=new Date();
//                endTime=date.getTime();
//                 logTotalTime=(int)(endTime-startTime);
//                 Log.getInstance().logRequest(clientConnection, serverConnection, cacheResult, logUrl, logLength, logFilter, logTotalTime);
//            }
//        }
//        try {
//            // disconnect from server.
//            disconnectServer();
//        }
//        catch( IOException ioe ) {
//            System.err.println("Error disconnecting from server: " + ioe.getMessage());
//        }
//        try {
//            // disconnect from client.
//            this.disconnectClient();
//        }
//        catch( IOException ioe ) {
//            System.err.println("Error disconnecting from client: " + ioe.getMessage());
//        }
    }
}
